package net.tp.util;

import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;

/**
 *
 * @author Trung Phan
 */
public class Wrappers {

    public static Iterable<Integer> sequence(int from, int to) {
        return () -> new Iterator<Integer>() {
                    private int i = from;
                    @Override
                    public boolean hasNext() {
                        return i < to;
                    }

                    @Override
                    public Integer next() {
                        if (!hasNext()) {
                            throw new NoSuchElementException();
                        }
                        return i++;
                    }

                    @Override
                    public void remove() {
                        throw new UnsupportedOperationException();
                    }
                };
    }


    public static <P, Q> Iterator<Q> makeIterator(Iterator<P> iterator, Function<P, Q> extractor) {
        if (iterator == null) {
            return Collections.emptyIterator();
        }

        return new Iterator<Q>() {
            @Override
            public boolean hasNext() {
                return iterator.hasNext();
            }

            @Override
            public Q next() {
                P next = iterator.next();
                Q value = extractor.apply(next);
                return value;
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }


    public static <N> Iterator<N> makeIterator(final N top, Function<N, N> next) {
        return new Iterator<N>() {
            private N node = top;
            @Override
            public boolean hasNext() {
                return node == null;
            }

            @Override
            public N next() {
                if (node == null) throw new NoSuchElementException();
                N n = node;
                node = next.apply(node);
                return n;
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }

    public static <P, Q> Iterable<Q> makeIterable(Iterable<P> iterable, Function<P, Q> extractor) {
        if (iterable == null) return Collections.emptyList();

        return () -> makeIterator(iterable.iterator(), extractor);
    }

    public static <Q> Iterable<Q> makeIterable(Iterable<Q> iterable) {
        if (iterable == null) return Collections.emptyList();
        return () -> iterable.iterator();
    }


    /**
     * Iterable that can iterate through all the iterables.
     * @param iterables array of iterables
     * @param <Q> element type
     * @return the combined iterable
     */
    @SafeVarargs
    public static <Q> Iterable<Q> makeIterable(Iterable<Q> ... iterables) {
        if (iterables == null || iterables.length == 0) return Collections.emptyList();
        // find the next iterator that has element

        Iterator<Q> _iterator = null;
        int i = 0;
        for (; i < iterables.length; i++) {
            Iterable<Q> iterable = iterables[i];
            _iterator = iterable.iterator();
            if (_iterator.hasNext()) break;
        }

        if (_iterator == null || !_iterator.hasNext()) return Collections.emptyList();
        final Iterator<Q> __iterator = _iterator;
        final int __i = i;

        return () -> new Iterator<Q>() {
            private Iterator<Q> iterator = __iterator;
            private int index = __i;
            @Override
            public boolean hasNext() {
                return iterator.hasNext();
            }
            @Override
            public Q next() {
                if (!hasNext()) throw new NoSuchElementException();
                Q next = iterator.next();
                if (!iterator.hasNext()) {
                    for (index = index+1; index < iterables.length; index++) {
                        iterator = iterables[index].iterator();
                        if (iterator.hasNext()) break;
                    }
                }

                return next;
            }
        };
    }

    @SafeVarargs
    public static <Q> Iterator<Q> makeIterator(Q ... elements) {
        return new Iterator<Q>(){
            private int index = 0;
            @Override
            public boolean hasNext() {
                return index < elements.length;
            }

            @Override
            public Q next() {
                return elements[index++];
            }
        };
    }

    @SafeVarargs
    @SuppressWarnings("varargs")
    public static <Q> Iterable<Q> makeIterable(Q ... elements) {
        return () -> makeIterator(elements);
    }

    public static Iterable<Character> makeIterable(final char[] chars) {
        return () -> new Iterator<Character>(){
            private int index = 0;
            @Override
            public boolean hasNext() {
                return index < chars.length;
            }

            @Override
            public Character next() {
                return chars[index++];
            }
        };
    }

    public static Iterable<Boolean> makeIterable(final boolean[] a) {
        return () -> new Iterator<Boolean>(){
            private int index = 0;
            @Override
            public boolean hasNext() {
                return index < a.length;
            }

            @Override
            public Boolean next() {
                return a[index++];
            }
        };
    }

    public static Iterable<Integer> makeIterable(final int[] a) {
        return () -> new Iterator<Integer>() {
            private int index = 0;
            @Override
            public boolean hasNext() {
                return index < a.length;
            }

            @Override
            public Integer next() {
                return a[index++];
            }
        };
    }

    public static <E> String iterableToString(Iterable<E> elements) {
        Iterator<E> it = elements.iterator();
        if (! it.hasNext())
            return "[]";

        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (;;) {
            E e = it.next();
            sb.append(e == elements ? "(this Collection)" : e);
            if (! it.hasNext())
                return sb.append(']').toString();
            sb.append(',').append(' ');
        }
    }

}
